package com.kelin.calendarlistview.library;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.*;

import ohos.app.Context;

import ohos.global.resource.NotExistException;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;

import ohos.media.image.PixelMap;

import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * PinnedHeaderListView
 */
public class PinnedHeaderListView extends ListView {

    private int mTriggerRefreshHeight;

    private boolean mTracking = false;

    private float mStartY = 0;

    private int mCurOffsetY = 0;

    private boolean mIsRefreshing = false;

    private boolean mCanRefresh = false;

    private PullDownStateListener mRefreshListener;

    private PullUpStateListener mPullUpListener;

    private Component mPullHeader = null;

    private Component mFirstHeader;

    private Component mContainer = null;

    private Image mBackgroundImageView = null;

    private int mMaxHeaderHeight = Integer.MAX_VALUE;

    private boolean mPullDownEnabled = true;

    private boolean mCanPullDown = true;

    private String mPullString;

    private String mRefreshingString;

    private String mReleaseString;

    private Text mMajorText;

    private Text mMinorText;

    private Image mIndicator;
    private ComponentContainer.LayoutConfig mContainerLayoutParams;

    protected LoadingFooter mLoadingFooter;

    protected LoadingFooter mLoadingHeader;
    private List<Component.ScrolledListener> mOnScrollListeners = new ArrayList<>();
    private PinnedSectionedHeaderAdapter mAdapter;
    private Component mCurrentHeader;
    private int mCurrentHeaderPos;
    private boolean mShouldPin = true;
    private int mCurrentSection = 0;
    protected boolean smoothScrolling = false;
    private int scrollPos;

    private OnSectionChangedListener onSectionChangedListener;

    /**
     * PinnedHeaderListView instance
     *
     * @param context 上下文
     */
    public PinnedHeaderListView(Context context) {
        super(context);
        init(context, null);
    }

    /**
     * PinnedHeaderListView
     *
     * @param context 上下文
     * @param attrs 属性
     */
    public PinnedHeaderListView(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    /**
     * PinnedHeaderListView instance
     *
     * @param context 上下文
     * @param attrs 属性
     * @param defStyle 名称
     */
    public PinnedHeaderListView(Context context, AttrSet attrs, String defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    /**
     * setPinHeaders
     *
     * @param shouldPin boolean
     */
    public void setPinHeaders(boolean shouldPin) {
        mShouldPin = shouldPin;
    }

    /**
     * setItemProvider
     *
     * @param itemProvider BaseItemProvider
     */
    public void setItemProvider(BaseItemProvider itemProvider) {
        mAdapter = (PinnedSectionedHeaderAdapter) itemProvider;
        getBody().setItemProvider(itemProvider);
    }

    private Component getSectionHeaderView(int section, Component oldView) {
        boolean shouldLayout = section != mCurrentSection;

        Component view = mAdapter.getSectionHeaderView(section, oldView, this);
        if (shouldLayout) {
            // a new section, thus a new header. We should lay it out again
            if (onSectionChangedListener != null && !smoothScrolling) {
                onSectionChangedListener.onSectionChanged(section, mCurrentSection);
            }
//            ensurePinnedHeaderLayout(view);
            mCurrentSection = section;
        }
        return view;
    }

    private void ensurePinnedHeaderLayout(Component header) {
        // if (header.isLayoutRequested()) {
        if (header.requestFocus()) {
            // int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
            int widthSpec = EstimateSpec.getSizeWithMode(getWidth(), EstimateSpec.PRECISE);
            int heightSpec;
            ComponentContainer.LayoutConfig layoutParams = header.getLayoutConfig();
            if (layoutParams != null && layoutParams.height > 0) {
                heightSpec = EstimateSpec.getSizeWithMode(layoutParams.height, EstimateSpec.PRECISE);
            } else {
                heightSpec = EstimateSpec.getSizeWithMode(0, EstimateSpec.UNCONSTRAINT);
            }
            // header.measure(widthSpec, heightSpec);
            header.estimateSize(widthSpec, heightSpec);
            int height = header.getEstimatedHeight();
            // header.layout(0, 0, getWidth(), height);
            header.setComponentPosition(0, 0, getWidth(), height);
        }
    }

    /**
     * addOnScrollListener
     *
     * @param l Component.ScrolledListener
     */
    public void addOnScrollListener(Component.ScrolledListener l) {
        if (mOnScrollListeners != null) {
            mOnScrollListeners.add(l);
        }
    }

    /**
     * removeOnScrollListener
     *
     * @param l Component.ScrolledListener
     */
    public void removeOnScrollListener(Component.ScrolledListener l) {
        if (mOnScrollListeners != null && mOnScrollListeners.contains(l)) {
            mOnScrollListeners.remove(l);
        }
    }

    /**
     * setOnItemClickListener
     *
     * @param listener ListContainer.ItemClickedListener
     */
    public void setOnItemClickListener(ListContainer.ItemClickedListener listener) {
        getBody().setItemClickedListener(listener);
    }

    /**
     * setOnSectionChangedListener
     *
     * @param onSectionChangedListener OnSectionChangedListener
     */
    public void setOnSectionChangedListener(OnSectionChangedListener onSectionChangedListener) {
        this.onSectionChangedListener = onSectionChangedListener;
    }

    private void init(Context context, AttrSet attrs) {
        // setHeaderDividersEnabled(false);
        ResourceManager resourceManager = getResourceManager();
        mLoadingFooter = new LoadingFooter(context);
        mLoadingFooter.setState(LoadingFooter.State.Idle);
        addFooterView(mLoadingFooter.getView());
        mLoadingHeader = new LoadingFooter(context);
        mLoadingHeader.setState(LoadingFooter.State.Loading);
        mTriggerRefreshHeight = 0;
        try {
            mTriggerRefreshHeight = CalendarHelper.vp2px(getContext(), resourceManager.getElement(ResourceTable.String_pulldown_trigger_refresh_height)
                    .getString());
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
        mPullHeader = LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_pulldown_header, null, false);
        mPullHeader.setClickedListener(new ClickedListener() {
            @Override
            public void onClick(Component component) {

            }
        });
        mContainer = mPullHeader.findComponentById(ResourceTable.Id_pull_header);
        mContainerLayoutParams = mContainer.getLayoutConfig();
        setContainerHeight(0);
        mBackgroundImageView = (Image) mPullHeader.findComponentById(ResourceTable.Id_img_bkg);
        mMajorText = (Text) mPullHeader.findComponentById(ResourceTable.Id_pull_header_major_text);
        mMinorText = (Text) mPullHeader.findComponentById(ResourceTable.Id_pull_header_minor_text);
        mIndicator = (Image) mPullHeader.findComponentById(ResourceTable.Id_pullheader_indicator);

        mFirstHeader = mPullHeader;

        addHeaderView(mPullHeader);

        try {
            mPullString = getResourceManager().getElement(ResourceTable.String_pulldown_pull).getString();
            ;
            mRefreshingString = getResourceManager().getElement(ResourceTable.String_pulldown_refreshing).getString();
            mReleaseString = getResourceManager().getElement(ResourceTable.String_pulldown_release).getString();
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }

        initScrolledListener();
    }

    private void initScrolledListener() {
        getBody().addScrolledListener(new ScrolledListener() {
            @Override
            public void onContentScrolled(Component component, int scrollX, int scrollY, int oldScrollX,
                                          int oldScrollY) {
                if (scrollPos >= 0) {
                    return;
                }
                pullToRefreshListViewOnScroll(scrollY, oldScrollY);
                if (mOnScrollListeners != null && mOnScrollListeners.size() > 0) {
                    for (Component.ScrolledListener listener : mOnScrollListeners) {
                        listener.onContentScrolled(component, scrollX, scrollY, oldScrollX,
                                oldScrollY);
                    }
                }
                fixedHeader();
            }

            @Override
            public void scrolledStageUpdate(Component component, int newStage) {
                if (newStage == SCROLL_IDLE_STAGE) {
                    if (scrollPos >= 0) {
                        int pos = scrollPos;
                        scrollPos = -1;
                        if (pos != getBody().getItemPosByVisibleIndex(0)) {
                            getBody().scrollTo(pos);
                            return;
                        }
                    }
                }

                if (mOnScrollListeners != null && mOnScrollListeners.size() > 0) {
                    Iterator<Component.ScrolledListener> it = mOnScrollListeners.iterator();
                    while (it.hasNext()) {
                        Component.ScrolledListener item = it.next();
                        item.scrolledStageUpdate(component, newStage);
                    }
                }
            }
        });
    }

    private void fixedHeader() {
        int firstVisibleItem = getBody().getItemPosByVisibleIndex(0);
//        int visibleItemCount = getBody().getVisibleIndexCount();

        if (mAdapter == null || mAdapter.getCount() == 0 || !mShouldPin || (firstVisibleItem < 1)) {
            if (mCurrentHeader != null) {
                getHeader().removeComponent(mCurrentHeader);
            }
            mCurrentHeader = null;
            return;
        }

//                firstVisibleItem -= getHeaderViewsCount();
        int section = mAdapter.getSectionForPosition(firstVisibleItem);
        if (mCurrentSection == section) {
            if (mCurrentHeader != null) {
                return;
            }
        } else {
            if (mCurrentHeader != null) {
                getHeader().removeComponent(mCurrentHeader);
            }
        }

//        int viewType = mAdapter.getSectionHeaderViewType(section);
        mCurrentHeader = getSectionHeaderView(section, null);

        mCurrentHeaderPos = firstVisibleItem;
        if (mAdapter.isSectionHeader(firstVisibleItem) && getBody().getComponentAt(mCurrentHeaderPos) != null) {
//            getBody().scrollBy(0, mCurrentHeader.getHeight());
//            getBody().getComponentAt(mCurrentHeaderPos).setVisibility(INVISIBLE);
        }
        getHeader().addComponent(mCurrentHeader);
    }

    private void setPointMove(float curY) {
        getBody().scrollTo(0, 0);
        mCurOffsetY = (int) (((double)curY - mStartY) / 2);
        if ((mCurOffsetY) < mMaxHeaderHeight) {
            setContainerHeight(mCurOffsetY);
            if (mCurOffsetY >= mTriggerRefreshHeight) {
                if (!mCanRefresh) {
                    mMajorText.setText(mReleaseString);
                    startRotateAnimation(mIndicator);
                    mCanRefresh = true;
                }
            } else {
                if (mCanRefresh) {
                    mMajorText.setText(mPullString);
                    startRotateBackAnimation(mIndicator);
                    mCanRefresh = false;
                }
            }
        } else {
            mCurOffsetY = Math.max(0, mMaxHeaderHeight);
        }
    }

    /**
     * setOnTouchEvent
     *
     * @param component Component
     * @param ev TouchEvent
     * @return boolean
     */
    public boolean setOnTouchEvent(Component component, TouchEvent ev) {
        if (!mCanPullDown) {
            return false;
        }
        switch (ev.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                if (mPullDownEnabled) {
                    mCanRefresh = false;
                    if (!mIsRefreshing && isOnTop()) {
                        prepareTracking(ev);
                        return true;
                    }
                }
                break;
            case TouchEvent.POINT_MOVE:
                if (mTracking) {
                    float curY = ev.getPointerScreenPosition(0).getY();

                    if (((double)curY - mStartY) > 10) {
                        setPointMove(curY);
                        return false;
                    }
                } else if (mPullDownEnabled && !mTracking && !mIsRefreshing && isOnTop()) {
                    prepareTracking(ev);
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                startBuncingBack();
                if (mTracking) {
                    if (mCanRefresh && mRefreshListener != null) {
                        mRefreshListener.onRefresh(PinnedHeaderListView.this);
                    }
                    stopTracking();
                }
                pullToRefreshListViewOnScroll((int) mStartY, (int) ev.getPointerScreenPosition(0).getY());
//                fixedHeader();
                break;
        }
        return false;
    }

    /**
     * getLoadingFooter
     *
     * @return LoadingFooter
     */
    public LoadingFooter getLoadingFooter() {
        return mLoadingFooter;
    }

    /**
     * pullToRefreshListViewOnScroll
     *
     * @param scrollY scrollY
     * @param oldScrollY oldScrollY
     */
    public void pullToRefreshListViewOnScroll(int scrollY, int oldScrollY) {
        if (mLoadingFooter.getState() == LoadingFooter.State.Loading
                || mLoadingFooter.getState() == LoadingFooter.State.TheEnd) {
            return;
        }
        int firstVisibleItem = getBody().getItemPosByVisibleIndex(0);
        int visibleItemCount = getBody().getVisibleIndexCount();
        int totalItemCount = getBody().getItemProvider().getCount();
        if (firstVisibleItem + visibleItemCount >= totalItemCount
                && totalItemCount != 0 && scrollY > oldScrollY) {
            mLoadingFooter.setState(LoadingFooter.State.Loading);
            if (mPullUpListener != null) {
                mPullUpListener.onLoadMore(this);
            }
        }
    }

    /**
     * setPullDownEnabled
     *
     * @param mPullDownEnabled boolean
     */
    public void setPullDownEnabled(boolean mPullDownEnabled) {
        this.mPullDownEnabled = mPullDownEnabled;
    }

    private void setContainerHeight(int height) {
        mContainerLayoutParams.height = height;
        // mContainer.setLayoutParams(mContainerLayoutParams);
        mContainer.setLayoutConfig(mContainerLayoutParams);
    }

    private int getContainerHeight() {
        return mContainerLayoutParams.height;
    }

    protected boolean isOnTop() {
        return getBody().getItemPosByVisibleIndex(0) <= 0 && mFirstHeader.getTop() >= 0;
    }

    private void prepareTracking(TouchEvent ev) {
        mMajorText.setText(mPullString);
        mTracking = true;
        mStartY = ev.getPointerScreenPosition(0).getY();
        if (mRefreshListener != null) {
            mRefreshListener.onPullDownStarted(PinnedHeaderListView.this);
        }
    }

    private void stopTracking() {
        mTracking = false;
    }

    /**
     * setBackgroundImage
     *
     * @param bitmap PixelMap
     */
    public void setBackgroundImage(PixelMap bitmap) {
        mBackgroundImageView.setPixelMap(bitmap);
    }

    /**
     * setIndicatorRes
     *
     * @param resId resId
     */
    public void setIndicatorRes(int resId) {
        // mIndicator.setImageResource(resId);
        mIndicator.setImageAndDecodeBounds(resId);
    }

    /**
     * getPullHeader
     *
     * @return Component
     */
    public Component getPullHeader() {
        return mPullHeader;
    }

    /**
     * getMajorTextView
     *
     * @return Text
     */
    public Text getMajorTextView() {
        return mMajorText;
    }

    /**
     * setMajorText
     *
     * @param text text
     */
    public void setMajorText(String text) {
        mMajorText.setText(text);
    }

    /**
     * setMinorText
     *
     * @param text text
     */
    public void setMinorText(String text) {
        mMinorText.setText(text);
    }

    /**
     * setPullString
     *
     * @param mPullString mPullString
     */
    public void setPullString(String mPullString) {
        this.mPullString = mPullString;
    }

    /**
     * setRefreshingString
     *
     * @param mRefreshingString mRefreshingString
     */
    public void setRefreshingString(String mRefreshingString) {
        this.mRefreshingString = mRefreshingString;
    }

    /**
     * setReleaseString
     *
     * @param mReleaseString mReleaseString
     */
    public void setReleaseString(String mReleaseString) {
        this.mReleaseString = mReleaseString;
    }

    /**
     * setPullDownStateListener
     *
     * @param listener PullDownStateListener
     */
    public void setPullDownStateListener(PullDownStateListener listener) {
        mRefreshListener = listener;
    }

    /**
     * setPullUpStateListener
     *
     * @param listener PullUpStateListener
     */
    public void setPullUpStateListener(PullUpStateListener listener) {
        mPullUpListener = listener;
    }

    /**
     * isRefreshing
     *
     * @return boolean
     */
    public boolean isRefreshing() {
        return mIsRefreshing;
    }

    /**
     * setRefreshing1
     *
     * @param refreshing boolean
     */
    public void setRefreshing1(boolean refreshing) {
        if (mIsRefreshing == refreshing) {
            return;
        }
        mIsRefreshing = refreshing;
        // View prog = findViewById(R.id.pull_header_prog);
        if (mIsRefreshing) {
            // mLoadingHeader.setState(State.Loading);
            addHeaderView(mLoadingHeader.getView());
        } else {
            removeHeaderView(mLoadingHeader.getView());
        }
    }

    /**
     * setRefreshing
     *
     * @param refreshing boolean
     */
    public void setRefreshing(boolean refreshing) {
        if (mIsRefreshing == refreshing) {
            return;
        }
        mIsRefreshing = refreshing;
        Component prog = findComponentById(ResourceTable.Id_pull_header_prog);
        if (mIsRefreshing) {
            // set headerHeight
//            if (getContainerHeight() < 50) {
                mCurOffsetY = mTriggerRefreshHeight;
                animateExpand();
//            }

            // mIndicator.clearAnimation();
            clearAnimation();
            mIndicator.setVisibility(Component.INVISIBLE);
            prog.setVisibility(Component.VISIBLE);
            LoadingFooter.startRotateAnimation(prog);
            mMajorText.setText(mRefreshingString);
        } else {
            mIndicator.setVisibility(Component.VISIBLE);
            mMajorText.setText(mPullString);
            prog.setVisibility(Component.INVISIBLE);
            LoadingFooter.stopRotateAnimation();
            startBuncingBack();
        }
    }

    /**
     * animateExpand
     */
    public void animateExpand() {
        setContainerHeight(mTriggerRefreshHeight);
    }

    /**
     * startBuncingBack
     */
    public void startBuncingBack() {
        setContainerHeight(0);
    }

    /**
     * PullDownStateListener
     */
    public static abstract class PullDownStateListener {
        public void onPullDownStarted(final PinnedHeaderListView listView) {
        }

        /**
         * onRefresh
         *
         * @param listView PinnedHeaderListView
         */
        public abstract void onRefresh(final PinnedHeaderListView listView);

        /**
         * onBouncingEnd
         *
         * @param listView PinnedHeaderListView
         */
        public void onBouncingEnd(final PinnedHeaderListView listView) {

        }
    }

    /**
     * PullUpStateListener
     */
    public static abstract class PullUpStateListener {
        public void onPullUpStarted(final PinnedHeaderListView listView) {

        }

        /**
         * onLoadMore
         *
         * @param listView PinnedHeaderListView
         */
        public abstract void onLoadMore(final PinnedHeaderListView listView);

        /**
         * onBouncingEnd
         *
         * @param listView PinnedHeaderListView
         */
        public void onBouncingEnd(final PinnedHeaderListView listView) {

        }
    }

    /**
     * OnSectionChangedListener
     */
    public static interface OnSectionChangedListener {
        void onSectionChanged(int newSection, int oldSection);
    }

    /**
     * smoothScrollToPosition
     *
     * @param position position
     * @param diff
     */
    public void smoothScrollToPosition(int position, int diff) {
//        smoothScrolling = true;
        scrollPos = -1;
        position += diff;
        getBody().scrollTo(position);
        Component child = getChildAtPosition(getBody(), position);
//        // There's no need to scroll if child is already at top or view is already
//        // scrolled to its end
        if ((child != null) && ((child.getTop() == 0) || ((child.getTop() > 0) && !this.getBody().canScroll(DRAG_VERTICAL)))) {
            return;
        }
        if (diff == 0) {
            return;
        }

        scrollPos = position;
    }

    /**
     * getChildAtPosition
     *
     * @param view ListContainer
     * @param position position
     * @return Component
     */
    public Component getChildAtPosition(final ListContainer view, final int position) {
        // final int index = position - view.getFirstVisiblePosition();
        final int index = position - view.getItemPosByVisibleIndex(0);
        if ((index >= 0) && (index < view.getChildCount())) {
            // return view.getChildAt(index);
            return view.getComponentAt(index);
        } else {
            return null;
        }
    }

    private void startRotateAnimation(Component component) {
//        mAnimRotate = AnimatorScatter.getInstance(getContext()).parse(ResourceTable.Graphic_rotate_180);
//        mAnimRotate.start();
        component.setRotation(0);
        AnimatorProperty animator = component.createAnimatorProperty();
        animator.setCurveType(Animator.CurveType.ANTICIPATE_OVERSHOOT);
        animator.rotate(180);
        animator.setDuration(300);
        animator.start();
    }

    private void startRotateBackAnimation(Component component) {
//        mAnimRotateBack = AnimatorScatter.getInstance(getContext())
//                .parse(ResourceTable.Graphic_rotate_back_180);
//        mAnimRotateBack.start();
        component.setRotation(180);
        AnimatorProperty animator = component.createAnimatorProperty();
        animator.setCurveType(Animator.CurveType.ANTICIPATE_OVERSHOOT);
        animator.rotate(0);
        animator.setDuration(300);
        animator.start();
    }

    private void clearAnimation() {
        // mAnimRotate.cancel();
        // mAnimRotateBack.cancel();
    }
}
